package org.elephantt.webby.security;

import javax.crypto.Mac;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeyException;
import java.util.Arrays;

import org.apache.commons.codec.binary.Base64;

/**
 * Generates and verifies sealed values using a MAC. A sealed value is tamper-proof: it guarantees integrity and authenticity.
 *
 * See http://en.wikipedia.org/wiki/Message_authentication_code
 */
public class SealedValueService {
  private static final String defaultHmacAlgo = "HmacSHA1";

  private final Mac macker;

  public SealedValueService(SecretKey key, String hmacAlgo) throws NoSuchAlgorithmException, InvalidKeyException {
    macker = Mac.getInstance(hmacAlgo);
    macker.init(key);
  }

  public SealedValueService(byte[] key, String hmacAlgo) throws InvalidKeyException, NoSuchAlgorithmException {
    this(new SecretKeySpec(key, hmacAlgo), hmacAlgo);
  }

  public SealedValueService(byte[] key) throws NoSuchAlgorithmException, InvalidKeyException {
    this(key, defaultHmacAlgo);
  }

  public SealedValueService(String base64Key) throws NoSuchAlgorithmException, InvalidKeyException {
    this(Base64.decodeBase64(base64Key.getBytes()));
  }

  /**
   * Creates a sealed value given the specified value
   */
  public SealedValue seal(byte[] value) {
    return new SealedValue(value, macker.doFinal(value));
  }

  /**
   * Returns true iff the sealed value has not been tampered.
   */
  public boolean verify(SealedValue value) {
    // compare computed mac to asserted mac
    byte[] actualMac = macker.doFinal(value.getValue());
    return Arrays.equals(actualMac, value.getMac());
  }

  public static void main(String[] args) throws NoSuchAlgorithmException, IOException, InvalidKeyException, ParseException {
    SealedValueService sts;
    if (args.length > 0) {
      String key = args[0];
      System.out.println("using provided key: " + key);
      sts = new SealedValueService(key);
    } else {
      KeyGenerator keygen = KeyGenerator.getInstance(defaultHmacAlgo);
      SecretKey key = keygen.generateKey();
      System.out.println("generated key: '" + new String(Base64.encodeBase64(key.getEncoded())) + "'");
      sts = new SealedValueService(key.getEncoded());
    }

    String value = "don't tread on me";
    System.out.println("value: " + value);
    SealedValue sealedValue = sts.seal(value.getBytes("UTF-8"));
    String encodedValue = sealedValue.toBase64String();
    System.out.println("base64-encoded sealed sealedValue: " + encodedValue);

    SealedValue value2 = SealedValue.parseBase64String(encodedValue);
    System.out.println("verified authentic token: " + sts.verify(value2));

    SealedValue value3 = SealedValue.parseBase64String(encodedValue + "z");
    System.out.println("verified tampered token: " + sts.verify(value3));
  }
}